package com.user.utils;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.user.dao.restaurant.RestaurantRecommend;
import com.user.dao.user.recommendation.OrderHistory;
import com.user.dao.user.recommendation.Restaurant;
import com.user.dao.user.recommendation.User;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author : Yu
 * @Date 2024/5/20 上午11:37
 */
public class RecommendRestaurants {
    private static final ObjectMapper objectMapper = new ObjectMapper();

    public static List<User> loadUsers(List<com.user.dao.restaurant.RecommendRestaurants> recommendRestaurants1) {
        Map<Integer, String> integerStringMap = RecommendRestaurants.classifyUserBasedOnAverageOrders(recommendRestaurants1);
        List<User> users = new ArrayList<>();
        integerStringMap.forEach((userId, classification) -> users.add(new User(userId, classification)));
        return users;
    }

    // 根据用户平均订单金额分类用户
    public static Map<Integer, String> classifyUserBasedOnAverageOrders(List<com.user.dao.restaurant.RecommendRestaurants> recommendRestaurants1) {
        Map<Integer, List<Double>> userOrderAmounts = new HashMap<>();
        Map<Integer, String> userClassifications = new HashMap<>();

        // Step 1: Aggregate order amounts for each user
        recommendRestaurants1.forEach(recommendRestaurant -> {
            userOrderAmounts.computeIfAbsent(recommendRestaurant.getId(), k -> new ArrayList<>()).add(recommendRestaurant.getOrderMoney());
        });

        // Step 2: Calculate average and classify
        userOrderAmounts.forEach((userId, amounts) -> {
            double average = amounts.stream().mapToDouble(a -> a).average().orElse(0.0);
            String classification = RecommendRestaurants.classifyOrderMoney(average);
            userClassifications.put(userId, classification);
        });

        return userClassifications;
    }

    // 根据订单金额分类用户
    private static String classifyOrderMoney(double orderMoney) {
        if (orderMoney < 50) return "low";
        else if (orderMoney < 100) return "medium";
        else return "high";
    }

    public static List<Restaurant> loadRestaurants(List<RestaurantRecommend> recommendRestaurants1) {

        List<Restaurant> restaurants = new ArrayList<>();
        for (RestaurantRecommend recommendRestaurants : recommendRestaurants1)
            restaurants.add(new Restaurant(recommendRestaurants.getRestaurantId(), recommendRestaurants.getPrice(), recommendRestaurants.getScore()));

        return restaurants;
    }

    public static List<OrderHistory> loadOrderHistories(List<com.user.dao.restaurant.RecommendRestaurants> recommendRestaurants1) throws IOException {
        List<OrderHistory> orderHistories = new ArrayList<>();
        for (com.user.dao.restaurant.RecommendRestaurants recommendRestaurants : recommendRestaurants1) {
            int sum = 0;
            String orderDetail = recommendRestaurants.getOrderDetail();
            Map<Integer, Integer> orderDetails = RecommendRestaurants.objectMapper.readValue(orderDetail, new TypeReference<Map<Integer, Integer>>() {
            });
            for (Map.Entry<Integer, Integer> entry : orderDetails.entrySet()) sum += entry.getValue();
            orderHistories.add(new OrderHistory(recommendRestaurants.getId(), recommendRestaurants.getRestaurantId(), sum, recommendRestaurants.getDishScore()));
        }
        return orderHistories;
    }


    public static List<Restaurant> recommendRestaurants(int userId, List<com.user.dao.restaurant.RecommendRestaurants> recommendRestaurants1, List<RestaurantRecommend> restaurantRecommends) throws IOException {
        List<User> users = RecommendRestaurants.loadUsers(recommendRestaurants1);
        List<Restaurant> restaurants = RecommendRestaurants.loadRestaurants(restaurantRecommends);
        List<OrderHistory> orderHistories = RecommendRestaurants.loadOrderHistories(recommendRestaurants1);

        User currentUser = users.stream()
                .filter(user -> user.getUserId() == userId)
                .findFirst()
                .orElse(null);

        if (currentUser == null) return new ArrayList<>();

        List<OrderHistory> currentUserHistories = orderHistories.stream()
                .filter(order -> order.getUserId() == userId)
                .collect(Collectors.toList());

        // 计算其他用户与当前用户的相似度
        Map<User, Double> similarityScores = new HashMap<>();
        for (User user : users)
            if (user.getUserId() != userId) {
                List<OrderHistory> otherUserHistories = orderHistories.stream()
                        .filter(order -> order.getUserId() == user.getUserId())
                        .collect(Collectors.toList());

                double similarity = RecommendRestaurants.calculate(currentUserHistories, otherUserHistories);
                similarityScores.put(user, similarity);
            }

        // 找到最相似的用户
        User mostSimilarUser = Collections.max(similarityScores.entrySet(), Map.Entry.comparingByValue()).getKey();
        List<OrderHistory> mostSimilarUserHistories = orderHistories.stream()
                .filter(order -> order.getUserId() == mostSimilarUser.getUserId())
                .collect(Collectors.toList());

        // 推荐餐厅
        List<Restaurant> recommendedRestaurants = new ArrayList<>();
        for (OrderHistory history : mostSimilarUserHistories)
            restaurants.stream()
                    .filter(r -> r.getRestaurantId() == history.getRestaurantId())
                    .findFirst().ifPresent(recommendedRestaurants::add);

        //删除重复的餐厅
        recommendedRestaurants = recommendedRestaurants.stream()
                .distinct()
                .collect(Collectors.toList());
        // 根据餐厅评分排序
        recommendedRestaurants.sort(Comparator.comparingDouble(Restaurant::getRating).reversed());


        return recommendedRestaurants;
    }

    public static double calculate(List<OrderHistory> user1Histories, List<OrderHistory> user2Histories) {
        double sum1 = 0;
        double sum2 = 0;
        double sum1Sq = 0;
        double sum2Sq = 0;
        double pSum = 0;
        int n = 0;

        for (OrderHistory oh1 : user1Histories)
            for (OrderHistory oh2 : user2Histories)
                if (oh1.getRestaurantId() == oh2.getRestaurantId()) {
                    double rating1 = oh1.getRating();
                    double rating2 = oh2.getRating();
                    sum1 += rating1;
                    sum2 += rating2;
                    sum1Sq += Math.pow(rating1, 2);
                    sum2Sq += Math.pow(rating2, 2);
                    pSum += rating1 * rating2;
                    n++;
                }

        if (n == 0) return 0;

        double num = pSum - (sum1 * sum2 / n);
        double den = Math.sqrt((sum1Sq - Math.pow(sum1, 2) / n) * (sum2Sq - Math.pow(sum2, 2) / n));
        if (den == 0) return 0;

        return num / den;
    }
}
